package xyz.zhzby.cyzx.judge.core;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import xyz.zhzby.cyzx.judge.config.ApplicationDispatcher;
import xyz.zhzby.cyzx.judge.entity.Checkpoint;
import xyz.zhzby.cyzx.judge.entity.Submission;
import xyz.zhzby.cyzx.judge.exception.IllgealSubmissionException;
import xyz.zhzby.cyzx.judge.service.*;
import xyz.zhzby.cyzx.judge.util.DigestUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author zby
 * @date 2022/02/04
 * @Description 评测机调度器. 用于完成评测机的评测流程. 每个阶段结束后推送消息至消息队列; 评测结束后写入数据库.
 */
@Component
@Slf4j
public class Dispatcher {

    @Autowired
    private ApplicationDispatcher applicationDispatcher;

    @Autowired
    private Preprocessor preprocessor;

    @Autowired
    private Compiler compiler;

    @Autowired
    private Runner runner;

    @Autowired
    private Comparator comparator;

    @Value("${judger.workDir}")
    private String workBaseDirectory;

    @Value("${judger.checkpointDir}")
    private String checkpointDirectory;

    @Autowired
    private ISubmissionService submissionService;

    @Autowired
    private ICheckpointService checkpointService;

    @Autowired
    private IProblemService problemService;

    @Autowired
    private ILanguageService languageService;

    /**
     * @Param [submissionId] [提交记录的ID]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 创建新的评测任务. 每次只运行一个评测任务.
     */
    public void createNewSubmissionTask(Long submissionId) throws IllgealSubmissionException {
        synchronized (this) {
            String baseDirectory =
                    String.format("%s/oj-%s", workBaseDirectory, submissionId);
            String baseFileName = DigestUtils.getRandomString(12, DigestUtils.Mode.ALPHA);

            // 解决由于未知原因无法获取到数据记录的问题
            int tryTimes = 0;
            Submission submission;
            do {
//                Thread.sleep(1000);
                submission = submissionService.getById(submissionId);
                submission.setLanguage(languageService.getById(submission.getLanguageId()));
                submission.setProblem(problemService.getById(submission.getProblemId()));
            } while (submission == null && ++tryTimes <= 3);

            if (submission == null) {
                throw new IllgealSubmissionException(String.format("非法评测任务 #%s", submissionId));
            }
            preprocess(submission, baseDirectory, baseFileName);
            if (compile(submission, baseDirectory, baseFileName)) {
                if (submission.getType() == 1) {
                    runProgram1(submission, baseDirectory, baseFileName);
                } else {
                    runProgram0(submission, baseDirectory, baseFileName);
                }
            }
            cleanUp(baseDirectory);
        }
    }

    /**
     * @Param [submission, workDirectory, baseFileName] [评测记录对象, 用于产生编译输出的目录, 随机文件名(不包含后缀)]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 完成评测前的预处理工作. 说明: 随机文件名用于防止应用程序自身递归调用.
     */
    private void preprocess(Submission submission, String workDirectory, String baseFileName) {
        try {
            Long problemId = submission.getProblem().getId();
            preprocessor.createTestCode(submission, workDirectory, baseFileName);
            if (submission.getType() == 1) {
                preprocessor.fetchTestPoints(problemId);
            } else {
                preprocessor.fetchInput(problemId, submission.getId(), submission.getJudgeLog());
            }
        } catch (Exception ex) {
            log.error(ex.getMessage());

            Long submissionId = submission.getId();
            applicationDispatcher.onErrorOccurred(submissionId);
        }
    }

    /**
     * @Param [submission, workDirectory, baseFileName] [评测记录对象, 用于产生编译输出的目录, 随机文件名(不包含后缀)]
     * @Return boolean []
     * @Author zby
     * @Date 2022/02/04
     * @Description 创建编译任务. 说明: 随机文件名用于防止应用程序自身递归调用.
     */
    private boolean compile(Submission submission, String workDirectory, String baseFileName) {
        Long submissionId = submission.getId();
        Map<String, Object> result = compiler.getCompileResult(submission, workDirectory, baseFileName);

        applicationDispatcher.onCompileFinished(submissionId, result);
        return (Boolean) result.get("isSuccessful");
    }

    /**
     * @Param [test, workDirectory, baseFileName] [测试记录对象, 编译生成结果的目录以及程序输出的目录, 待执行的应用程序文件名(不包含文件后缀)]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 执行程序.
     */
    private void runProgram0(Submission submission, String workDirectory, String baseFileName) {
        Long submissionId = submission.getId();
        Long problemId = submission.getProblem().getId();

        String inputFilePath = String.format("%s/%s/input#%s.txt", checkpointDirectory, problemId, submissionId);
        String stdOutputFilePath = String.format("%s/%s/output#%s.txt", checkpointDirectory, problemId, submissionId);
        String outputFilePath = getOutputFilePath(workDirectory, submissionId);

        Map<String, Object> runtimeResult = getRuntimeResult(
                runner.getRuntimeResult(submission, workDirectory, baseFileName, inputFilePath, outputFilePath),
                getOutputFilePath(workDirectory, submissionId), outputFilePath);
        applicationDispatcher.onTestFinished(submissionId, runtimeResult);
    }

    /**
     * @Param [submission, workDirectory, baseFileName] [评测记录对象, 编译生成结果的目录以及程序输出的目录, 待执行的应用程序文件名(不包含文件后缀)]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 执行程序.
     */
    private void runProgram1(Submission submission, String workDirectory, String baseFileName) {
        List<Map<String, Object>> runtimeResults = new ArrayList<>();
        Long submissionId = submission.getId();
        Long problemId = submission.getProblem().getId();

        LambdaQueryWrapper<Checkpoint> qw = new LambdaQueryWrapper<>();
        qw.eq(Checkpoint::getProblemId, problemId);
        List<Checkpoint> checkpoints = checkpointService.list(qw);
        for (Checkpoint checkpoint : checkpoints) {
            Long checkpointId = checkpoint.getId();
            int checkpointScore = checkpoint.getScore();
            String inputFilePath = String.format("%s/%s/input#%s.txt", checkpointDirectory, problemId, checkpointId);
            String stdOutputFilePath = String.format("%s/%s/output#%s.txt", checkpointDirectory, problemId, checkpointId);
            String outputFilePath = getOutputFilePath(workDirectory, checkpointId);

            Map<String, Object> runtimeResult = getRuntimeResult(
                    runner.getRuntimeResult(submission, workDirectory, baseFileName, inputFilePath, outputFilePath),
                    stdOutputFilePath, outputFilePath);
            runtimeResult.put("score", checkpointScore);
            runtimeResults.add(runtimeResult);
            applicationDispatcher.onOneTestPointFinished(submissionId, checkpointId, runtimeResult);
        }
        applicationDispatcher.onAllTestPointsFinished(submissionId, runtimeResults);
    }

    /**
     * @Param [workDirectory, checkpointId] [编译生成结果的目录以及程序输出的目录, 当前测试点编号]
     * @Return java.lang.String [当前测试点输出路径]
     * @Author zby
     * @Date 2022/02/04
     * @Description 获取当前测试点输出路径.
     */
    private String getOutputFilePath(String workDirectory, Long checkpointId) {
        return String.format("%s/output#%s.txt", workDirectory, checkpointId);
    }

    /**
     * @Param [result, standardOutputFilePath, outputFilePath] [包含程序运行结果的Map对象, 标准输出文件路径, 用户输出文件路径]
     * @Return java.util.Map<java.lang.String, java.lang.Object> [包含程序运行结果的Map对象]
     * @Author zby
     * @Date 2022/02/04
     * @Description 获取程序运行结果(及答案比对结果).
     */
    private Map<String, Object> getRuntimeResult(
            Map<String, Object> result, String standardOutputFilePath, String outputFilePath) {
        String runtimeResultSlug = (String) result.get("runtimeResult");
        int usedTime = (Integer) result.get("usedTime");
        int usedMemory = (Integer) result.get("usedMemory");

        if ("AC".equals(runtimeResultSlug)
                && !isOutputTheSame(standardOutputFilePath, outputFilePath)) {
            runtimeResultSlug = "WA";
            result.put("runtimeResult", runtimeResultSlug);
        }
        result.put("runtimeOutput", getOutput(standardOutputFilePath));
        log.info(String.format("RuntimeResult: [%s, Time: %d ms, Memory: %d KB]", runtimeResultSlug, usedTime, usedMemory));

        return result;
    }

    /**
     * @Param [standardOutputFilePath, outputFilePath] [标准输出文件路径, 用户输出文件路径]
     * @Return boolean [用户输出和标准输出是否相同]
     * @Author zby
     * @Date 2022/02/04
     * @Description 获取用户输出和标准输出的比对结果.
     */
    private boolean isOutputTheSame(String standardOutputFilePath, String outputFilePath) {
        try {
            return comparator.isOutputTheSame(standardOutputFilePath, outputFilePath);
        } catch (IOException ex) {
            log.error(ex.getMessage());
        }
        return false;
    }

    private String getOutput(String standardOutputFilePath) {
        try {
            return comparator.getOutput(standardOutputFilePath);
        } catch (IOException e) {
            log.error(e.getMessage());
            return "System Error";
        }
    }

    /**
     * @Param [baseDirectory] [用于产生输出结果目录]
     * @Return void []
     * @Author zby
     * @Date 2022/02/04
     * @Description 评测完成后, 清理所生成的文件.
     */
    private void cleanUp(String baseDirectory) {
        File baseDirFile = new File(baseDirectory);
        if (baseDirFile.exists()) {
            try {
                FileUtils.deleteDirectory(baseDirFile);
            } catch (IOException ex) {
                log.error(ex.getMessage());
            }
        }
    }
}
